﻿using System;
using System.ServiceModel;
using System.Threading;
using System.Configuration;
using System.ServiceModel.Configuration;
using BacteriumObserver;
using System.Reflection;
using System.ServiceModel.Discovery;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommonDataContracts;
using System.Net;
using System.Diagnostics;
using System.Net.Sockets;
using System.Linq;

namespace Bacterium
{
    class BacteriumHelper
    {

        private static Uri _hostBaseAddress;
        private static EndpointAddress _defaultObserverEndpoint;    

        static void Main(string[] args)
        {

            _hostBaseAddress = new UriBuilder("http://", 
                Dns.GetHostName(), 
                GetFreeTcpPort())
                .Uri;

            Console.Title = String.Format("Host: {0} ", _hostBaseAddress);
            var b = new Bacterium();
  
            //Opens up the host for incoming requests from other bacteria
            using (ServiceHost host = new ServiceHost(b, _hostBaseAddress))
            {                
                host.Open();
                
                
                Console.Write("  Bacterium service host is running..\n    Press enter when ready to launch client part");
                Console.ReadLine();
         
                while (true)
                {
                    Thread.Sleep(5000);

                    b.PerformAction();                    
                }
                
                host.Close();                
            }
        }
        
#region Calls to the Observer         

        #region Calls to multiple observers

        public static void RegisterWithDomains(List<Uri> domains, BacteriumSnapshot b)
        {
            
                foreach (var observerMetadata in DiscoverMultipleObservers(domains))
                {
                    try
                    {
                        var observerProxy = new BacteriumObserverClient(new BasicHttpBinding(), 
                            observerMetadata.Address);
                    
                        Console.WriteLine("  Attempting to register with observer \n  at {0}",
                            observerMetadata.Address.Uri);

                        var watch = Stopwatch.StartNew();

                        observerProxy.RegisterWithDomain(GetHostEndpoint(), 
                            b);

                        watch.Stop();
                        Console.WriteLine("Took {0} ms to respond", 
                            watch.ElapsedMilliseconds);
                    
                    }

                    catch (Exception e)
                    {
                        Console.WriteLine("ERROR: {0}", e.Message);                        
                        
                        //may want to repeat the last iteration
                    }              
            }
            
        }

        public static void UpdateObservers(List<Uri> domains, String prop, String value)
        {   
            
                foreach (var observerMetadata in DiscoverMultipleObservers(domains))
                {
                    try
                    {
                        var observerProxy = new BacteriumObserverClient(new BasicHttpBinding(), observerMetadata.Address);

                        Console.WriteLine("  Attempting to send UpdateBacteriumProperty message to observer \n  at {0}",                            
                            observerProxy.Endpoint.Address);

                        var watch = Stopwatch.StartNew();
                        observerProxy.UpdateBacteriumProperty(GetHostEndpoint(), prop, value);

                        watch.Stop();
                        Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);
                    }

                    catch (Exception e)
                    {
                        Console.WriteLine("ERROR: {0}", e.Message);
                        //ReinitializeObserverEndpoint();
                    }
                
            }
            
        }

        #endregion

        public static IBacterium GetNeutralBacterium()
        {
            var observerProxy = CreateObserverClient();
            Console.WriteLine("  Attempting to send GetNeutralBacterium message to observer \n  at {0}",                
                observerProxy.Endpoint.Address);

            try
            {
                var watch = Stopwatch.StartNew();

                var result = observerProxy.GetNeutralBacterium(GetHostEndpoint());
                
                watch.Stop();
                Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);

                if (result != String.Empty)
                    return InitializeTargetProxy(result);
                return null;
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                //ReinitializeObserverEndpoint();

                return null;
            }
 
        }

        public static Boolean GetTeamBalance()
        {
            var observerProxy = CreateObserverClient();
            Console.WriteLine("  Attempting to send GetTeamBalance message to observer \n  at {0}",                
                observerProxy.Endpoint.Address);

            try
            {
                var watch = Stopwatch.StartNew();

                var result = observerProxy.GetTeamBalance(GetHostEndpoint());

                watch.Stop();
                Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);

                return result;
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                //ReinitializeObserverEndpoint();

                return false;
            }
        }

        public static Boolean GetPowerRatio()
        {
            var observerProxy = CreateObserverClient();
            Console.WriteLine("  Attempting to send GetPowerRatio message to observer \n  at {0}",                
                observerProxy.Endpoint.Address);

            try
            {
                var watch = Stopwatch.StartNew();

                var result = observerProxy.GetPowerRatio(GetHostEndpoint());

                watch.Stop();
                Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);

                return result;
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                //ReinitializeObserverEndpoint();

                return false;
            }
        }

        public static IBacterium GetWeakestEnemy()
        {
            var observerProxy = CreateObserverClient();
            Console.WriteLine("  Attempting to send GetWeakestEnemy message to observer \n  at {0}",                
                observerProxy.Endpoint.Address);

            try
            {
                var watch = Stopwatch.StartNew();

                var result = observerProxy.GetWeakestEnemy(GetHostEndpoint());

                watch.Stop();
                Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);

                if (result != String.Empty)
                    return InitializeTargetProxy(result);
                return null;
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                //ReinitializeObserverEndpoint();

                return null;
            }
        }

        public static IBacterium GetWeakestAlly()
        {
            var observerProxy = CreateObserverClient();
            Console.WriteLine("  Attempting to send GetWeakestAlly message to observer \n  at {0}",
               observerProxy.Endpoint.Address);

            try
            {
                var watch = Stopwatch.StartNew();

                var result = observerProxy.GetWeakestAlly(GetHostEndpoint());

                watch.Stop();
                Console.WriteLine("Took {0} ms to respond", watch.ElapsedMilliseconds);

                if (result != String.Empty)
                    return InitializeTargetProxy(result);
                return null;
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                //ReinitializeObserverEndpoint();

                return null;
            }
        }

#endregion

#region Discovery and Proxy instantiation routines

        public static void ReinitializeObserverEndpoint(List<Uri> domains)
        {
            _defaultObserverEndpoint = DiscoverMultipleObservers(domains, true)[0].Address;

            Console.Title = String.Format("Bacterium: {0} Observer: {1} ",                
                GetHostEndpoint(), 
                _defaultObserverEndpoint != null 
                    ? _defaultObserverEndpoint.ToString() 
                    :"No Observer" );           
           
        }

        

        private static IBacterium InitializeTargetProxy(String endpoint)
        {
            ChannelFactory<IBacterium> httpFactory = new ChannelFactory<IBacterium>(new BasicHttpBinding(), endpoint);
            Console.Title = String.Format("Host: {0} Target: {1}", GetHostEndpoint(), endpoint);
            return httpFactory.CreateChannel();
        }

       

        private static Collection<EndpointDiscoveryMetadata> DiscoverMultipleObservers(List<Uri> scopes, Boolean isOnlyOneNeeded = false, int duration = 1)
        {
            var discoClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
            var criteria = new FindCriteria(typeof(IBacteriumObserver))
            {
                Duration = TimeSpan.FromSeconds(duration)
            };

            if (isOnlyOneNeeded)
                criteria.MaxResults = 1;

            foreach (var scope in scopes)
            {
                criteria.Scopes.Add(scope);
            }

            criteria.ScopeMatchBy = new Uri("net.tcp://Microsoft.Samples.Discovery/ORExactMatch");

            try
            {
                Console.WriteLine("  Observer discovery in progress..");

                var response = discoClient.Find(criteria);
                Console.WriteLine("  {0} observer endpoint(s) discovered.", 
                    response.Endpoints.Count);

                if (response.Endpoints.Count > 0)
                {
                    return response.Endpoints;
                }
                else 
                {
                    var randomTimeout = new Random().Next(1000);
                    Console.WriteLine("Discovery will retry in {0} ms.", 
                        randomTimeout);

                    Thread.Sleep(randomTimeout);

                    return DiscoverMultipleObservers(scopes, isOnlyOneNeeded, ++duration);
                }
            }
            catch (Exception e)
            {

                Console.WriteLine("ERROR: {0}", e.Message);
                return null;
            }

            
        }

        private static BacteriumObserverClient CreateObserverClient()
        {
            if (_defaultObserverEndpoint != null)
                return new BacteriumObserverClient(new BasicHttpBinding(), _defaultObserverEndpoint);
            return null;
        }

        static int GetFreeTcpPort()
        {
            TcpListener l = new TcpListener(IPAddress.Parse("127.0.0.1"), 0);
            l.Start();
            int port = ((IPEndPoint)l.LocalEndpoint).Port;
            l.Stop();
            return port;
        }

#endregion

#region Getting Data from config

        public static String GetTargetEndpoint()
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ServiceModelSectionGroup section = config.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup;
            return section.Client.Endpoints[0].Address.OriginalString; 
        }

        public static String GetHostEndpoint()
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ServicesSection section = config.GetSection("system.serviceModel/services") as ServicesSection;

            return _hostBaseAddress + section.Services[0].Endpoints[0].Address.OriginalString;
        }

        public static String GetObserverEndpoint()
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ServiceModelSectionGroup section = config.GetSectionGroup("system.serviceModel") as ServiceModelSectionGroup;
            return section.Client.Endpoints[1].Address.OriginalString;
        }

#endregion
    }


}
